package paulscode.sound;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 * The StreamThread class is used to process all streaming sources. This thread
 * starts out asleep, and it sleeps when all streaming sources are finished
 * playing, so it is necessary to call interrupt() after adding new streaming
 * sources to the list. <br>
 * <br>
 * <b><i> SoundSystem License:</b></i><br>
 * <b><br>
 * You are free to use this library for any purpose, commercial or otherwise.
 * You may modify this library or source code, and distribute it any way you
 * like, provided the following conditions are met: <br>
 * 1) You may not falsely claim to be the author of this library or any
 * unmodified portion of it. <br>
 * 2) You may not copyright this library or a modified version of it and then
 * sue me for copyright infringement. <br>
 * 3) If you modify the source code, you must clearly document the changes made
 * before redistributing the modified source code, so other users know it is not
 * the original code. <br>
 * 4) You are not required to give me credit for this library in any derived
 * work, but if you do, you must also mention my website:
 * http://www.paulscode.com <br>
 * 5) I the author will not be responsible for any damages (physical, financial,
 * or otherwise) caused by the use if this library or any part of it. <br>
 * 6) I the author do not guarantee, warrant, or make any representations,
 * either expressed or implied, regarding the use of this library or any part of
 * it. <br>
 * <br>
 * Author: Paul Lamb <br>
 * http://www.paulscode.com </b>
 * 
 */
public class StreamThread extends SimpleThread {
	/**
	 * Processes status messages, warnings, and error messages.
	 */
	private SoundSystemLogger logger;

	/**
	 * List of sources that are currently streaming.
	 */
	private List<Source> streamingSources;

	/**
	 * Used to synchronize access to the streaming sources list.
	 */
	private final Object listLock = new Object();

	/**
	 * Constructor: Grabs a handle to the message logger and instantiates the
	 * streaming sources list.
	 */
	public StreamThread() {
		// grab a handle to the message logger:
		logger = SoundSystemConfig.getLogger();

		streamingSources = new LinkedList<Source>();
	}

	/**
	 * Removes all references to instantiated objects, and changes the thread's
	 * state to "not alive". Method alive() returns false when the cleanup()
	 * method has completed.
	 */
	@Override
	protected void cleanup() {
		kill();
		super.cleanup(); // Important!!
	}

	/**
	 * The main loop for processing commands. The thread sleeps when it finishes
	 * processing commands, and it must be interrupted to process more.
	 */
	@Override
	public void run() {
		ListIterator<Source> iter;
		Source src;

		// Start out asleep:
		snooze(3600000);

		while (!dying()) {
			while (!dying() && !streamingSources.isEmpty()) {
				// Make sure noone else is accessing the list of sources:
				synchronized (listLock) {
					iter = streamingSources.listIterator();
					while (!dying() && iter.hasNext()) {
						src = iter.next();
						if (src == null) {
							iter.remove();
						} else if (src.stopped()) {
							if (!src.rawDataStream)
								iter.remove();
						} else if (!src.active()) {
							if (src.toLoop || src.rawDataStream)
								src.toPlay = true;
							iter.remove();
						} else if (!src.paused()) {
							src.checkFadeOut();
							if ((!src.stream()) && (!src.rawDataStream)) {
								if (src.channel == null
										|| !src.channel.processBuffer()) {
									// check if this is a looping source
									if (src.toLoop) {
										// wait for stream to finish playing
										if (!src.playing()) {
											// Generate an EOS event:
											SoundSystemConfig
													.notifyEOS(
															src.sourcename,
															src.getSoundSequenceQueueSize());
											// Check if the source is currently
											// in the process of fading out.
											if (src.checkFadeOut()) {
												// Source is fading out.
												// Keep looping until it
												// finishes.
												src.preLoad = true;
											} else {
												// Source is not fading out.
												// If there is another sound in
												// the sequence, switch to it
												// before replaying.
												src.incrementSoundSequence();
												src.preLoad = true; // replay
											}
										}
									} else {
										// wait for stream to finish playing
										if (!src.playing()) {
											// Generate an EOS event:
											SoundSystemConfig
													.notifyEOS(
															src.sourcename,
															src.getSoundSequenceQueueSize());
											// Check if the source is currently
											// in the process of fading out
											if (!src.checkFadeOut()) {
												// Source is not fading out.
												// Play anything else that is
												// in the sound sequence queue.
												if (src.incrementSoundSequence())
													src.preLoad = true;
												else
													iter.remove(); // finished
											}
										}
									}
								}
							}
						}
					}
				}
				if (!dying() && !streamingSources.isEmpty())
					snooze(20); // sleep a bit so we don't peg the cpu
			}
			if (!dying() && streamingSources.isEmpty())
				snooze(3600000); // sleep until there is more to do.
		}

		cleanup(); // Important!!
	}

	/**
	 * Adds a new streaming source to the list. If another source in the list is
	 * already playing on the same channel, it is stopped and removed from the
	 * list.
	 * 
	 * @param source
	 *            New source to stream.
	 */
	public void watch(Source source) {
		// make sure the source exists:
		if (source == null)
			return;

		// make sure we aren't already watching this source:
		if (streamingSources.contains(source))
			return;

		ListIterator<Source> iter;
		Source src;

		// Make sure noone else is accessing the list of sources:
		synchronized (listLock) {
			// Any currently watched source which is null or playing on the
			// same channel as the new source should be stopped and removed
			// from the list.
			iter = streamingSources.listIterator();
			while (iter.hasNext()) {
				src = iter.next();
				if (src == null) {
					iter.remove();
				} else if (source.channel == src.channel) {
					src.stop();
					iter.remove();
				}
			}

			// Add the new source to the list:
			streamingSources.add(source);
		}
	}

	/**
	 * Prints a message.
	 * 
	 * @param message
	 *            Message to print.
	 */
	private void message(String message) {
		logger.message(message, 0);
	}

	/**
	 * Prints an important message.
	 * 
	 * @param message
	 *            Message to print.
	 */
	private void importantMessage(String message) {
		logger.importantMessage(message, 0);
	}

	/**
	 * Prints the specified message if error is true.
	 * 
	 * @param error
	 *            True or False.
	 * @param message
	 *            Message to print if error is true.
	 * @return True if error is true.
	 */
	private boolean errorCheck(boolean error, String message) {
		return logger.errorCheck(error, "StreamThread", message, 0);
	}

	/**
	 * Prints an error message.
	 * 
	 * @param message
	 *            Message to print.
	 */
	private void errorMessage(String message) {
		logger.errorMessage("StreamThread", message, 0);
	}
}
